package pl.squeaky.mvc.view;

import java.util.ArrayList;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE.SharedImages;
import org.eclipse.ui.part.ViewPart;

import pl.squeaky.mvc.Activator;
import pl.squeaky.mvc.model.IAction;
import pl.squeaky.mvc.model.IController;
import pl.squeaky.mvc.model.IControllers;
import pl.squeaky.mvc.model.IModel;
import pl.squeaky.mvc.model.IModels;
import pl.squeaky.mvc.model.IView;
import pl.squeaky.mvc.model.IViews;
import pl.squeaky.mvc.model.MvcPackage;
import pl.squeaky.mvc.nature.MvcNature;

public class MvcView extends ViewPart {
	private TreeViewer viewer;

	class ViewContentProvider implements ITreeContentProvider {
		
		public void inputChanged(Viewer v, Object oldInput, Object newInput) {
		}
		
		public void dispose() {
		}
		
		public Object[] getElements(Object parent) {
			IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
			
			ArrayList<IProject> list = new ArrayList<IProject>();
			for(IProject project : root.getProjects()) {
				if(!project.isOpen()) {
					continue;
				}
				try {
					if(project.hasNature(MvcNature.NATURE_ID)) {
						list.add(project);
					}
				} catch (CoreException e) {
					throw new RuntimeException(e);
				}
			}
			
			return list.toArray();
		}
		
		@Override
		public Object[] getChildren(Object parentElement) {
			if(parentElement instanceof IProject) {
				IProject project = (IProject)parentElement;				
				MvcNature nature;
				try {
					if(!project.hasNature(MvcNature.NATURE_ID)) {
						return new Object[0];
					}
					nature = (MvcNature)project.getNature(MvcNature.NATURE_ID);
				} catch (CoreException e) {
					throw new RuntimeException(e);
				}
				IControllers controllers = nature.getControllers();
				IModels models = nature.getModels();
				IViews views = nature.getViews();
				
				controllers.eAdapters().add(modelListner);
				models.eAdapters().add(modelListner);
				views.eAdapters().add(modelListner);
				

				return new Object[] {controllers, models, views};
			} else if(parentElement instanceof IControllers) {
				IControllers controllers = (IControllers)parentElement;
				
				for(IController controller : controllers.getCollection()) {
					controller.eAdapters().add(modelListner);
				}
				
				return controllers.getCollection().toArray();
			} else if(parentElement instanceof IController) {
				IController controller = (IController)parentElement;
				
				return controller.getActions().toArray();
			} else if(parentElement instanceof IModels) {
				IModels models = (IModels)parentElement;
				
				for(IModel model : models.getCollection()) {
					model.eAdapters().add(modelListner);
				}
				
				return models.getCollection().toArray();
			} else if(parentElement instanceof IViews) {
				IViews views = (IViews)parentElement;
				
				for(IView view : views.getCollection()) {
					view.eAdapters().add(modelListner);
				}
				
				return views.getCollection().toArray();
			}
			
			return new Object[0];
		}
		@Override
		public Object getParent(Object element) {
			if(element instanceof IProject) {
				return null;
			} else if(element instanceof IControllers) {
				IControllers controllers = (IControllers)element;
				
				return controllers.getNature().getProject();
			} else if(element instanceof IController) {
				IController controller = (IController)element;
				
				return controller.getParent();
			} else if(element instanceof IAction) {
				IAction action = (IAction)element;
				
				return action.getController();
			} else if(element instanceof IModels) {
				IModels models = (IModels)element;
				
				return models.getNature().getProject();
			} else if(element instanceof IModel) {
				IModel model = (IModel)element;
				
				return model.getParent();
			} else if(element instanceof IViews) {
				IViews views = (IViews)element;
				
				return views.getNature().getProject();
			} else if(element instanceof IView) {
				IView view = (IView)element;
				
				return view.getParent();
			}
			
			return null;
		}
		
		@Override
		public boolean hasChildren(Object element) {
			if(element instanceof IProject) {
				return true;
			} else if(element instanceof IControllers) {
				IControllers controllers = (IControllers)element;
				return !controllers.getCollection().isEmpty();
			} else if(element instanceof IController) {
				IController controller = (IController)element;
				return !controller.getActions().isEmpty();
			} else if(element instanceof IModels) {
				IModels models = (IModels)element;
				
				return !models.getCollection().isEmpty();
			} else if(element instanceof IViews) {
				IViews views = (IViews)element;
				
				return !views.getCollection().isEmpty();
			}
			
			return false;
		}
	}
	
	class ViewLabelProvider extends LabelProvider {
		@Override
		public String getText(Object element) {
			if(element instanceof IProject) {
				IProject project = (IProject)element;
				return project.getName();
			} else if(element instanceof IControllers) {
				return "Controllers";
			} else if(element instanceof IModels) {
				return "Models";
			} else if(element instanceof IViews) {
				return "Views";
			} else if(element instanceof IController) {
				IController controller = (IController)element;
				
				return controller.getName();
			} else if(element instanceof IAction) {
				IAction action = (IAction)element;
				
				return action.getName();
			} else if(element instanceof IView) {
				IView view = (IView)element;
				
				return view.getName();
			} else if(element instanceof IModel) {
				IModel model = (IModel)element;
				
				return model.getName();
			}
			
			return null;
		}
		
		@Override
		public Image getImage(Object element) {
			if(element instanceof IProject) {
				return PlatformUI.getWorkbench().getSharedImages().getImage(SharedImages.IMG_OBJ_PROJECT);
			} else if(element instanceof IControllers) {
				return Activator.getDefault().getImageRegistry().get("controllers");
			} else if(element instanceof IViews) {
				return Activator.getDefault().getImageRegistry().get("views");
			} else if(element instanceof IModels) {
				return Activator.getDefault().getImageRegistry().get("models");
			} else if(element instanceof IController) {
				return Activator.getDefault().getImageRegistry().get("controller");
			} else if(element instanceof IAction) {
				return Activator.getDefault().getImageRegistry().get("action");
			} else if(element instanceof IView) {
				return Activator.getDefault().getImageRegistry().get("view");
			} else if(element instanceof IModel) {
				return Activator.getDefault().getImageRegistry().get("model");
			}
			
			return null;
		}
	}
	
	class ResourceChangeListener implements IResourceChangeListener {
		class ResourceDeltaVisitor implements IResourceDeltaVisitor {
			@Override
			public boolean visit(IResourceDelta delta) throws CoreException {
				//TODO: remove
                System.out.println("Change: " + delta.getResource().getFullPath().toString());
				switch(delta.getKind()) {
				case IResourceDelta.ADDED:
					System.out.println("ADDED");
					break;
				case IResourceDelta.CHANGED:
					System.out.println("CHNAGED");
					break;
				case IResourceDelta.REMOVED:
					System.out.println("REMOVED");
					break;
				}
				
				if((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) {
					System.out.println("F: DESCRIPTION");
				} else if((delta.getFlags() & IResourceDelta.OPEN) != 0) {
					System.out.println("F: OPEN");
				}
				
				if (delta.getResource() instanceof IWorkspaceRoot) {
					return true;
				} else if(delta.getResource() instanceof IProject) {
					System.out.println(delta.getResource().getName());
					if(delta.getKind() == IResourceDelta.ADDED) {
						viewer.add(getSite(), delta.getResource());
						viewer.refresh(delta.getResource());
					} else if(delta.getKind() == IResourceDelta.REMOVED) {
						viewer.remove(delta.getResource());
					}
					
					return false;
				}
				
				return true;
			}
			
		}
		
		@Override
		public void resourceChanged(IResourceChangeEvent event) {
			switch(event.getType()) {
			case IResourceChangeEvent.POST_CHANGE:
				System.out.println("--- POST_CHANGE ---");
				break;
			case IResourceChangeEvent.PRE_BUILD:
				System.out.println("--- PRE_BUILD ---");
				break;
			case IResourceChangeEvent.POST_BUILD:
				System.out.println("--- POST_BUILD ---");
				break;
			case IResourceChangeEvent.PRE_CLOSE:
				System.out.println("--- PRE_CLOSE ---");
				break;
			case IResourceChangeEvent.PRE_DELETE:
				System.out.println("--- PRE_DELTE ---");
				break;
			}
			
			try {
				event.getDelta().accept(new ResourceDeltaVisitor());
				viewer.refresh();
			} catch(CoreException e) {
				throw new RuntimeException(e);
			}
		}
		
	}
	
	private ModelChangeListner modelListner = new ModelChangeListner();
	
	class ModelChangeListner extends AdapterImpl {
		@Override
		public void notifyChanged(Notification msg) {
			Display display = PlatformUI.getWorkbench().getDisplay();
			//TODO: remove
			System.out.println("ModelChangeListener: " + msg.getNotifier().getClass().getCanonicalName());
			
			Object notifier = msg.getNotifier();
			if(notifier instanceof IControllers) {
				final IControllers controllers = (IControllers)notifier;
				
				if(msg.getFeatureID(IControllers.class) == MvcPackage.ICONTROLLERS__COLLECTION) {
					if(msg.getEventType() == Notification.ADD) {
						final IController controller = (IController)msg.getNewValue();
						controller.eAdapters().add(this);
						System.out.println("SIZE: " + controller.eAdapters().size());
						
						display.asyncExec(new Runnable() {
							@Override
							public void run() {
								viewer.add(controllers, controller);
								viewer.refresh(controllers);								
							}
						});
					} else if(msg.getEventType() == Notification.REMOVE) {
						final IController controller = (IController)msg.getOldValue();
						controller.eAdapters().remove(this);
						
						display.asyncExec(new Runnable() {
							@Override
							public void run() {
								viewer.remove(controller);
								viewer.refresh(controllers);
							}
						});
					}
				}
			} else if(notifier instanceof IController) {
				final IController controller = (IController)notifier;
				
				if(msg.getEventType() == Notification.ADD) {
					final IAction action = (IAction)msg.getNewValue();
					action.eAdapters().add(this);
					
					display.asyncExec(new Runnable() {
						@Override
						public void run() {
							viewer.add(controller, action);
							viewer.refresh(controller);
						}
					});
					
				} else if(msg.getEventType() == Notification.REMOVE) {
					final IAction action = (IAction)msg.getOldValue();
					action.eAdapters().remove(this);
					
					display.asyncExec(new Runnable() {
						@Override
						public void run() {
							viewer.remove(action);
							viewer.refresh(controller);
						}
					});
				}
			} else if(notifier instanceof IModels) {
				final IModels models = (IModels)notifier;
				
				if(msg.getEventType() == Notification.ADD) {
					final IModel model = (IModel)msg.getNewValue();
					model.eAdapters().add(this);
					
					display.asyncExec(new Runnable() {
						@Override
						public void run() {
							viewer.add(models, model);
							viewer.refresh(models);
						}
					});
				} else if(msg.getEventType() == Notification.REMOVE) {
					final IModel model = (IModel)msg.getOldValue();
					model.eAdapters().remove(this);
					
					display.asyncExec(new Runnable() {
						@Override
						public void run() {
							viewer.remove(model);
							viewer.refresh(models);
						}
					});
				}
			} else if(notifier instanceof IViews) {
				final IViews views = (IViews)notifier;
				
				if(msg.getEventType() == Notification.ADD) {
					final IView view = (IView)msg.getNewValue();
					view.eAdapters().add(this);
					
					display.asyncExec(new Runnable() {
						@Override
						public void run() {
							viewer.add(views, view);
							viewer.refresh(views);
						}
					});
				} else if(msg.getEventType() == Notification.REMOVE) {
					final IView view = (IView)msg.getOldValue();
					view.eAdapters().remove(this);
					
					display.asyncExec(new Runnable() {
						@Override
						public void run() {
							viewer.remove(view);
							viewer.refresh();
						}
					});
				}
			}
		}
	}
	
	class NameSorter extends ViewerSorter {
	}

	public MvcView() {
	}

	public void createPartControl(Composite parent) {
		viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
		viewer.setContentProvider(new ViewContentProvider());
		viewer.setLabelProvider(new ViewLabelProvider());
		viewer.setSorter(new NameSorter());
		viewer.setInput(getViewSite());
		
		viewer.addDoubleClickListener(new MvcExplorerDoubleClickListener());
		
		ResourcesPlugin.getWorkspace().addResourceChangeListener(new ResourceChangeListener(), IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_BUILD | IResourceChangeEvent.POST_BUILD | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.PRE_CLOSE);
	}

	public void setFocus() {
		viewer.getControl().setFocus();
	}
}